package com.ynsj.aspect;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSON;
import com.ynsj.model.WebLog;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import springfox.documentation.spring.web.json.Json;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author 86130
 */
@Component
@Aspect
@Order(1)
@Slf4j
public class WebLogAspect {

    /**
     * 切入com.ynsj.controller包下的任意类任意方法任意参数
     * 定义切点
     * 第一个* 代表任意返回类型
     * 第二个* 代表任意类
     * 第三个* 代表任意方法
     * .. 代表任意参数
     */
    @Pointcut("execution(* com.ynsj.controller.*.*(..))")
    public void webLog(){}

    /**
     * 环绕通知
     */
    @Around("webLog()")
    public Object recordWebLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = null;
        //计算方法运行时间
        //创建定时器
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        //不需要自己捕获异常，运行业务代码
        result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
        stopWatch.stop();
        //获取当前请求的request对象
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();

        //获取安全上下文
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        String url = request.getRequestURL().toString();
        //build WebLog对象
        WebLog webLog = new WebLog();
        //方法执行耗费时间
        webLog.setSpendTime((int) stopWatch.getTotalTimeMillis());
        //TODO 获取IP地址 后续完善
        webLog.setIp(request.getRemoteAddr());
        //设置请求的uri
        webLog.setUri(request.getRequestURI());
        webLog.setUrl(url);
        //获取到跟路径信息
        webLog.setBasePath(StrUtil.removeSuffix(url, URLUtil.url(url).getPath()));
        //获取当前登录用户名
        webLog.setUsername(authentication == null ? "anonymous" : authentication.getPrincipal().toString());

        //获取方法签名
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        //获取方法
        Method method = signature.getMethod();
        //获取类名称
        String targetClassName = proceedingJoinPoint.getTarget().getClass().getName();
        //获取@ApiOperation注解
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        webLog.setMethod(targetClassName+"."+method.getName());
        //获取格式为{"key:参数名称":"value:参数值"}
        webLog.setParameter(getMethodParameter(method,proceedingJoinPoint.getArgs()));
        webLog.setResult(result);
        //在swagger2中 我们会使用 @ApiOperation注解来描述接口
        webLog.setDescription(apiOperation == null ? "no desc":apiOperation.value());
        log.info(JSON.toJSONString(webLog,true));
        return result;


    }

    /**
     * 获取参数列表
     * @param method
     * @param args
     * @return
     */
    private Object getMethodParameter(Method method, Object[] args) {
        Map<String, Object> methodParameterMap = new HashMap<>();
        // 反射获取类的所有信息
        LocalVariableTableParameterNameDiscoverer localVariableTableParameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
        //获取参数名称
        String[] parameterNames = localVariableTableParameterNameDiscoverer.getParameterNames(method);
        if (parameterNames!=null&&parameterNames.length>0){
            //组装参数列表和值
            for (int i = 0; i < parameterNames.length ; i++) {
                if ("password".equals(parameterNames[i]) || "file".equals(parameterNames[i])){
                    methodParameterMap.put(parameterNames[i],"受限的支持类型");
                }else {
                    methodParameterMap.put(parameterNames[i], args[i]);
                }
            }
        }
        return methodParameterMap;
    }
}
